//
//  String+AD.swift
//  ADKit
//
//  Created by Adoma on 2020/10/22.
//

import Foundation
import CommonCrypto
import CryptoKit

public extension String {
    
    /// by ADKit
    func md5() -> String {
        
        let inputData = Data(self.utf8)

        let hashData: any Sequence<UInt8>
        
        if #available(iOS 13, *) {
            hashData = Insecure.MD5.hash(data: inputData)
        } else {
            hashData = inputData.md5()
        }

        let result = hashData.map { String(format: "%02hhx", $0) }.joined()

        return result
    }
    
    /// by ADKit
    func sha1() -> String {
        
        let inputData = Data(self.utf8)

        let hashData: any Sequence<UInt8>
        
        if #available(iOS 13, *) {
            hashData = Insecure.SHA1.hash(data: inputData)
        } else {
            hashData = inputData.sha1()
        }

        let result = hashData.map { String(format: "%02hhx", $0) }.joined()

        return result
    }
    
    /// by ADKit
    func base64() -> String? {
        guard let data = self.data(using: .utf8) else {
            return nil
        }
        return data.base64EncodedString()
    }
}

public extension String{
    
    /// by ADKit
    func toPinYin() -> String {
        
        let mutableString = NSMutableString(string: self)
        
        CFStringTransform(mutableString, nil, kCFStringTransformToLatin, false)
        CFStringTransform(mutableString, nil, kCFStringTransformStripDiacritics, false)
        
        return mutableString.capitalized
    }
    
    /// by ADKit
    func toAttributed(using: String.Encoding = .unicode) -> NSMutableAttributedString? {
        try? NSMutableAttributedString(html: self, using: using)
    }
    
    /// by ADKit
    func size(for font: UIFont, size: CGSize) -> CGSize {
        return (self as NSString).boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: [.font: font], context: nil).size
    }
    
    func width(for font: UIFont) -> CGFloat {
        let size = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
        return self.size(for: font, size: size).width
    }
 
    func size(for font: UIFont, width: CGFloat) -> CGSize {
        let size = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
        return self.size(for: font, size: size)
    }
    
    func height(for font: UIFont, width: CGFloat) -> CGFloat {
        return self.size(for: font, width: width).height
    }
}

public extension String.SubSequence {
    
    /// by ADKit
    var toString: String { String(self) }
    
}

public extension Character {
    
    /// by ADKit
    var toString: String { String(self) }
    
}

public extension NSAttributedString {
    
    /// by ADKit
    convenience init(string: String, color: UIColor, font: UIFont) {
        self.init(string: string, attributes: [.foregroundColor: color, .font: font])
    }
    
    convenience init(string: String, font: UIFont, color: UIColor) {
        self.init(string: string, attributes: [.foregroundColor: color, .font: font])
    }
    
    /// by ADKit
    convenience init(html: String, using: String.Encoding = .unicode) throws {
        guard let data = html.data(using: using) else {
            throw ADError.error(message: "To Data failed!")
        }
        try self.init(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
    }
}

public extension NSMutableAttributedString {
    
    /// by ADKit
    var ad_rangeOfAll: NSRange {
        .init(location: 0, length: length)
    }
    
    /// by ADKit
    @discardableResult
    func append(string: String, color: UIColor, font: UIFont) -> NSMutableAttributedString {
        append(NSAttributedString(string: string, color: color, font: font))
        return self
    }
    
    /// by ADKit
    @discardableResult
    func append(string: NSAttributedString) -> NSMutableAttributedString {
        append(string)
        return self
    }
    
    /// by ADKit
    @discardableResult
    func add(attribute name: NSAttributedString.Key, value: Any, range: NSRange? = nil) -> NSMutableAttributedString {
        let range = range ?? ad_rangeOfAll
        addAttribute(name, value: value, range: range)
        return self
    }
    
    /// by ADKit
    @discardableResult
    func add(attributes: [NSAttributedString.Key : Any], range: NSRange? = nil) -> NSMutableAttributedString {
        let range = range ?? ad_rangeOfAll
        addAttributes(attributes, range: range)
        return self
    }
}

public extension String {
    
    /// by ADKit
    func substring(to index: Int) -> String {
        prefix(index + 1).toString
    }
    
    /// by ADKit
    func substring(from index: Int) -> String {
        suffix(max(count - index - 1, 0)).toString
    }
    
    subscript (i: Int) -> String {
        return self[index(startIndex, offsetBy: i)].toString
    }
    
    subscript (bounds: CountableRange<Int>) -> String {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        if end < start { return "" }
        return self[start..<end].toString
    }
    
    subscript (bounds: CountableClosedRange<Int>) -> String {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        if end < start { return "" }
        return self[start...end].toString
    }
    
    subscript (bounds: CountablePartialRangeFrom<Int>) -> String {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(endIndex, offsetBy: -1)
        if end < start { return "" }
        return self[start...end].toString
    }
    
    subscript (bounds: PartialRangeThrough<Int>) -> String {
        let end = index(startIndex, offsetBy: bounds.upperBound)
        if end < startIndex { return "" }
        return self[startIndex...end].toString
    }
    
    subscript (bounds: PartialRangeUpTo<Int>) -> String {
        let end = index(startIndex, offsetBy: bounds.upperBound)
        if end < startIndex { return "" }
        return self[startIndex..<end].toString
    }
}
